/*
 * Copyright 2018 the original author or authors.
 * 
 * you can use the file or import project.but Do not take legal responsibility.
 * Only for learning communication.
 *
 * Java compiled version 1.8 or higher
 * 
 * 
 * 
 * 
 */
 
package org.demo.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.TimeZone;


/**
 * 【日期工具类】<br>
 * <ul>
 * 	<li>字符日期格式化日期，format前缀</li>
 * 	<li>解析字符串日期为date类型，parse前缀</li>
 * 	<li>时间计算，calc前缀</li>
 * 	<li>计算时间差，calcDiff</li>
 * </ul>
 * @author jx-air
 * @version V1.0
 * @date 2017年10月11日 下午2:59:13
 */
public abstract class ExaDateUtil {
	

    /**
     * 默认日期格式：yyyy-MM-dd
     */
    public static final String FORMAT_DATE_DEFAULT = "yyyy-MM-dd";
    /**
     * 默认时间格式：HH:mm:ss
     */
    public static final String FORMAT_TIME = "HH:mm:ss";
    /**
     * 默认日期时间格式 ，日期格式yyyy-MM-dd HH:mm:ss
     */
    public static final String FORMAT_DATE_TIME_DEFAULT = "yyyy-MM-dd HH:mm:ss";

    /**
     * 紧凑型日期时间格式，格式：yyyyMMddHHmmss
     */
    public static final String FORMAT_DATE_TIME_TIGHT = "yyyyMMddHHmmss";
    /**
     * 日期格式,yyyy-MM-dd hh:mi
     */
    public static final String FORMAT_DATE_TIME_MIN = "yyyy-MM-dd HH:mm";
    /**
     * 紧凑型日期格式，格式：yyyyMMdd
     */
    public static final String FORMAT_DATE_TIGHT = "yyyyMMdd";
    /**
     * 精度到年，例如在时间比较
     */
    public static final int ACCURACY_YEAR = 6;
    /**
     * 精度到月
     */
    public static final int ACCURACY_MONTH = 5;
    /**
     * 精度到天
     */
    public static final int ACCURACY_DAY = 4;
    /**
     * 精度到时
     */
    public static final int ACCURACY_HH = 3;
    /**
     * 精度到分
     */
    public static final int ACCURACY_MM = 2;
    /**
     * 精度到秒
     */
    public static final int ACCURACY_SS = 1;
    /**自定义错误代码*/
//    private static final long ERROR_CODE = 999999996L;
    
    private static final TimeZone GMT = TimeZone.getTimeZone("GMT");//时区
    /** 能够解析日期格式集合 */
    private static final Collection<String> DEFAULT_PATTERNS = Arrays.asList(new String[] {FORMAT_DATE_DEFAULT, FORMAT_DATE_TIME_DEFAULT, FORMAT_DATE_TIME_TIGHT } );

    private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
    
    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2000, Calendar.JANUARY, 1, 0, 0);
        DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); 
    }
    
    /**
     * 格式化时间
     *
     * @param date date类型的日期
     * @param format 需要转换的格式，建议使用DateUtil.FORMAT_xxx来获取格式
     * @return
     */
    public static String formatDate(Date date, String format) {
    	if (date == null) throw new IllegalArgumentException("date is null");
        if (format == null) throw new IllegalArgumentException("pattern is null");
        
        SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.US);
        formatter.setTimeZone(GMT);
        return formatter.format(date);
    }
    
    /**
     *	格式化当前日期
     *
     * @param pattern 需要转换的格式，建议使用DateUtil.FORMAT_xxx来获取格式
     * @return
     */
    public static String formatDate(String format) {
    	
    	return formatDate(new Date(),format);
    }
    
    /**
     * 使用默认格式，格式化日期:yyyy-MM-dd
     * @param date
     * @return
     */
    public static String formatDate(Date date) {
        
        return formatDate(date,FORMAT_DATE_DEFAULT);
    }

    /**
     * 解析日期
     * @param dateValue
     * @return
     * @throws DateParseException
     */
    public static Date parseDate(String dateValue) throws Exception {
        return parseDate(dateValue, null, null);
    }
    /**
     * 解析日期
     * @param dateValue
     * @return
     * @throws DateParseException
     */
    public static Date parseDate(String dateValue,String format) throws Exception {
    	if (dateValue == null) {
            throw new IllegalArgumentException("dateValue is null");
        }
        if (format == null) {
        	 throw new IllegalArgumentException("format is null");
        }
        SimpleDateFormat dateParser = new SimpleDateFormat(format, Locale.US);
        dateParser.setTimeZone(TimeZone.getTimeZone("GMT"));
    	return parseDate(dateValue, null, null);
    }
    
    /**
     * 解析日期
     * @param dateValue 待解析的字符串日期
     * @param dateFormats 日期支持的字符串格式
     * @return
     * @throws DateParseException
     */
    public static Date parseDate(String dateValue, Collection<String> dateFormats) throws Exception {
            return parseDate(dateValue, dateFormats, null);
     }
    
    /**
     * 解析日期
     * @param dateValue 待解析的字符串日期
     * @param dateFormats 日期支持的字符串格式
     * @param startDate
     * @return
     * @throws DateParseException
     */
    public static Date parseDate(String dateValue, Collection<String> dateFormats,Date startDate) throws Exception {
            
            if (dateValue == null) {
                throw new IllegalArgumentException("dateValue is null");
            }
            if (dateFormats == null) {
                dateFormats = DEFAULT_PATTERNS;
            }
            if (startDate == null) {
                startDate = DEFAULT_TWO_DIGIT_YEAR_START;
            }
            // trim single quotes around date if present
            // see issue #5279
            if (dateValue.length() > 1 && dateValue.startsWith("'")  && dateValue.endsWith("'")) {
                dateValue = dateValue.substring (1, dateValue.length() - 1);
            }
            
            SimpleDateFormat dateParser = null;        
            Iterator<String> formatIter = dateFormats.iterator();
            
            while (formatIter.hasNext()) {
                String format = (String) formatIter.next();            
                if (dateParser == null) {
                    dateParser = new SimpleDateFormat(format, Locale.US);
                    dateParser.setTimeZone(TimeZone.getTimeZone("GMT"));
                    dateParser.set2DigitYearStart(startDate);
                } else {
                    dateParser.applyPattern(format);                    
                }
                try {
                    return dateParser.parse(dateValue);
                } catch (ParseException pe) {
                }                
            }
            
            throw new Exception("Unable to parse the date " + dateValue);        
        }
    
	/**
	 * 增加或减少日期后的时间
	 * @param date 指定日期
	 * @param time 增加或减少的时间，增加为整数，减少为负数
	 * @param calcType 计算类型，支持年、月、日、时、分、秒 。建议使用ACCURACY_xxx
	 * @return
	 */
	public static Date calc(Date date, int change,int calcType){
		checkDateNull(date);
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		if(ACCURACY_YEAR==calcType) {
			calendar.add(Calendar.YEAR, change);
		}else if(ACCURACY_MONTH==calcType) {
			calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH)+change);
		}else if(ACCURACY_DAY==calcType) {
			calendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH)+change);
		}else if(ACCURACY_HH==calcType) {
			calendar.add(Calendar.HOUR, change);
		}else if(ACCURACY_MM==calcType) {
			calendar.add(Calendar.MINUTE, change);
		}else if(ACCURACY_SS==calcType) {
			calendar.add(Calendar.SECOND, change);
		}else {
			checkDateTypeError();
		}
		
		return calendar.getTime();
	}
	
	/**
	 * 增加或减少日期后的时间，已当前时间为基准，往前或往后推移change时间后的时间
	 * @param time 增加或减少的时间，增加为整数，减少为负数
	 * @param calcType 计算类型，支持年、月、日、时、分、秒 。建议使用ACCURACY_xxx
	 * @return
	 */
	public static Date calc(int change,int calcType) {
		return calc(new Date(),change,calcType);
	}
	/**
	 * 计算时差，
	 * @param time 增加或减少的时间，增加为整数，减少为负数
	 * @param calcType 计算类型，支持年、月、日、时、分、秒 。建议使用ACCURACY_xxx
	 * @return
	 */
	/**
	 * 
	 * @param date1 第一个时间
	 * @param date2
	 * @param format
	 * @param calcType
	 * @return
	 * @throws Exception 
	 */
	public static long calcDiff(String date1,String date2,String format,int calcType) throws Exception {
		if (date1 == null) throw new IllegalArgumentException("date1 is null");
		if (date2 == null) throw new IllegalArgumentException("date2 is null");
		if (format == null) throw new IllegalArgumentException("format is null");
		
		Date d1 = parseDate(date1,format);
		Date d2 = parseDate(date2,format);
		
		return calcDiff(d1,d2,calcType);
	}
	
	/**
	 * 计算时差
	 * @param date1  开始时间
	 * @param date2  结束时间
	 * @param calcType
	 * @return
	 */
	public static long calcDiff(Date date1,Date date2,int calcType) {
		checkDateNull(date1);
		checkDateNull(date2);
		long diff=0;
		diff =  date2.getTime() - date1.getTime();
		
		if(ACCURACY_YEAR==calcType || ACCURACY_MONTH==calcType) {
			diff = calcYearOrMonth(date1,date2,calcType);
		}else if(ACCURACY_DAY==calcType) {
			diff = (long)(diff/(1000*60*60*24));
		}else if(ACCURACY_HH==calcType) {
			diff = (long)(diff/(1000*60*60));
		}else if(ACCURACY_MM==calcType) {
			diff = (long)(diff/(1000*60));
		}else if(ACCURACY_SS==calcType) {
			diff = (long)(diff/1000);
		}else {
			checkDateTypeError();
		}
		
		return diff;
	}
	
	/**
	 * 计算两个时间相差多少年、月
	 * @param date1
	 * @param date2
	 * @return
	 */
	private static int calcYearOrMonth(Date date1,Date date2,int calcType) {
		Calendar c1 = Calendar.getInstance();
		Calendar c2 = Calendar.getInstance();
		c1.setTime(date1);
		c2.setTime(date2);
		
		if(ACCURACY_YEAR==calcType) {
			return c2.get(Calendar.YEAR)-c1.get(Calendar.YEAR);
		}else if(ACCURACY_MONTH==calcType) {
			return c2.get(Calendar.MONTH)-c1.get(Calendar.MONTH);
		}else {
			throw new IllegalArgumentException("date calcType not exist");
		}
	}
	
	private static void checkDateNull(Date date) {
		if (date == null) throw new IllegalArgumentException("date1 is null");
	}
	
	private static void checkDateTypeError() {
		throw new IllegalArgumentException("date calcType not exist");
	}

}
